﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using MvcContrib.Pagination;

namespace MvcContrib.Shp.UI.Themed.Pager
{
    public class PagerThemed : IHtmlString
    {
        private readonly IPagination _pagination;
        private readonly ViewContext _viewContext;

        private string _paginationFormat = "Showing {0} - {1} of {2} ";
        private string _paginationSingleFormat = "Showing {0} of {1} ";
        private string _paginationFirst = "first page";
        private string _paginationPrev = "previous page";
        private string _paginationNext = "next page";
        private string _paginationLast = "last page";
        private string _pageQueryName = "page";
        
        private PagerPosition _pagerPosition = PagerPosition.Bottom;
        private string _pageSizeQueryName = "size";
        private bool _sizeable = true;
        private IEnumerable<int> _pageSizes = new List<int> { 10, 20, 50 };

        /// <summary>
        /// Creates a new instance of the Pager class.
        /// </summary>
        /// <param name="pagination">The IPagination datasource</param>
        /// <param name="context">The view context</param>
        public PagerThemed(IPagination pagination, ViewContext context)
        {
            _pagination = pagination;
            _viewContext = context;
        }

        protected ViewContext ViewContext
        {
            get { return _viewContext; }
        }


        /// <summary>
        /// Specifies the query string parameter to use when generating pager links. The default is 'page'
        /// </summary>
        public PagerThemed QueryParam(string queryStringParam)
        {
            _pageQueryName = queryStringParam;
            return this;
        }

        /// <summary>
        /// Specifies the query size parameter to use when generating pager links. The default is 'size'
        /// </summary>
        public PagerThemed QuerySizeParam(string queryStringParam)
        {
            _pageSizeQueryName = queryStringParam;
            return this;
        }

        /// <summary>
        /// Set the Pager's page size select list items. The defaults are 10, 20, and 50.
        /// </summary>
        public PagerThemed PageSizes(IEnumerable<int> pageSizes)
        {
            _pageSizes = pageSizes;
            return this;
        }

        /// <summary>
        /// Specifies the format to use when rendering a pagination containing a single page. 
        /// The default is 'Showing {0} of {1}' (eg 'Showing 1 of 3')
        /// </summary>
        public PagerThemed SingleFormat(string format)
        {
            _paginationSingleFormat = format;
            return this;
        }

        /// <summary>
        /// Specifies the format to use when rendering a pagination containing multiple pages. 
        /// The default is 'Showing {0} - {1} of {2}' (eg 'Showing 1 to 3 of 6')
        /// </summary>
        public PagerThemed Format(string format)
        {
            _paginationFormat = format;
            return this;
        }

        /// <summary>
        /// Text for the 'first' link.
        /// </summary>
        public PagerThemed First(string first)
        {
            _paginationFirst = first;
            return this;
        }

        /// <summary>
        /// Text for the 'prev' link
        /// </summary>
        public PagerThemed Previous(string previous)
        {
            _paginationPrev = previous;
            return this;
        }

        /// <summary>
        /// Text for the 'next' link
        /// </summary>
        public PagerThemed Next(string next)
        {
            _paginationNext = next;
            return this;
        }

        /// <summary>
        /// Text for the 'last' link
        /// </summary>
        public PagerThemed Last(string last)
        {
            _paginationLast = last;
            return this;
        }

        /// <summary>
        /// Set the Pager's the position.
        /// </summary>
        /// <param name="position">The position of the pager.</param>
        /// <returns></returns>
        public PagerThemed Position(PagerPosition position)
        {
            _pagerPosition = position;
            return this;
        }

        /// <summary>
        /// Enables/Disable the Pager's page-size select list.  Default is true/enabled.
        /// </summary>
        public PagerThemed Sizeable(bool sizeable)
        {
            _sizeable = sizeable;
            return this;
        }

        // For backwards compatibility with WebFormViewEngine
        public override string ToString()
        {
            return ToHtmlString();
        }

        public string ToHtmlString()
        {
            if (_pagination.TotalItems == 0) return null;

            var builder = new StringBuilder();

            // logic form pager positioning - this modifies the border roundness
            var cornerClass = " ui-corner-bottom";

            switch (_pagerPosition)
            {
                case PagerPosition.Top:
                    cornerClass = " ui-corner-top";
                    break;
                case PagerPosition.Bottom:
                    break;
                case PagerPosition.Between:
                    cornerClass = string.Empty;
                    break;
            }

            builder.AppendFormat("<div class=\"mc-grid-footer ui-widget-header{0} ui-helper-clearfix\">", cornerClass);

            if (_pagination.TotalPages > 1) RenderRightSideOfPager(builder);

            RenderLeftSideOfPager(builder);

            builder.Append("</div>");

            return builder.ToString();
        }

        protected virtual void RenderLeftSideOfPager(StringBuilder builder)
        {
            builder.Append("<div class=\"mc-grid-pager-results\">");

            //Special case handling where the page only contains 1 item (ie it's a details-view rather than a grid)
            if (_pagination.PageSize == 1)
            {
                RenderNumberOfItemsWhenThereIsOnlyOneItemPerPage(builder);
            }
            else
            {
                RenderNumberOfItemsWhenThereAreMultipleItemsPerPage(builder);
            }

            builder.Append("</div>");
        }

        protected virtual void RenderRightSideOfPager(StringBuilder builder)
        {
            builder.Append("<div class=\"mc-grid-pager ui-helper-clearfix\">");

            // page size dropdown
            builder.Append(CreatePageSizeDropdown());

            // first page button
            builder.Append(CreatePageLink(1, PagingType.First, (_pagination.PageNumber == 1)));

            // previous page button
            builder.Append(CreatePageLink((_pagination.PageNumber - 1), PagingType.Previous, !_pagination.HasPreviousPage));

            // next page button
            builder.Append(CreatePageLink((_pagination.PageNumber + 1), PagingType.Next, !_pagination.HasNextPage));

            int lastPage = _pagination.TotalPages;

            // last page button
            builder.Append(CreatePageLink(lastPage, PagingType.Last, !(_pagination.PageNumber < lastPage)));

            builder.Append("</div>");
        }

        protected virtual void RenderNumberOfItemsWhenThereIsOnlyOneItemPerPage(StringBuilder builder)
        {
            builder.AppendFormat(_paginationSingleFormat, _pagination.FirstItem, _pagination.TotalItems);
        }

        protected virtual void RenderNumberOfItemsWhenThereAreMultipleItemsPerPage(StringBuilder builder)
        {
            builder.AppendFormat(_paginationFormat, _pagination.FirstItem, _pagination.LastItem, _pagination.TotalItems);
        }

        private string CreatePageSizeDropdown()
        {
            if (!_sizeable || (_pageSizes == null) || (_pageSizes.Count() < 1)) return string.Empty;

            var select = new TagBuilder("select");
            select.AddCssClass("mc-grid-page-size");

            var optionBuilder = new StringBuilder();

            var span = new TagBuilder("span");
            span.AddCssClass("mc-grid-page-size");
            span.InnerHtml = "Page size:";

            foreach (var pageSize in _pageSizes)
            {
                var size = Math.Abs(pageSize);
                var option = new TagBuilder("option");
                option.MergeAttribute("value", CreatePageSizeUrl(size));
                if (size == _pagination.PageSize) option.MergeAttribute("selected", "selected");
                option.InnerHtml = size.ToString();

                optionBuilder.Append(option.ToString(TagRenderMode.Normal));
            }
            select.InnerHtml = optionBuilder.ToString();
            return span.ToString(TagRenderMode.Normal) + select.ToString(TagRenderMode.Normal);
        }

        private string CreatePageLink(int pageNumber, PagingType pagingType, bool emptyLink)
        {
            var url = (emptyLink) ? "javascript:void(0);" : CreatePagerUrl(pageNumber);
            var buttonClass = "mc-grid-paging ui-state-default ui-corner-left";
            var iconClass = "ui-icon ui-icon-seek-first";
            var title = _paginationFirst;

            switch (pagingType)
            {
                case PagingType.First:
                    break;
                case PagingType.Previous:
                    buttonClass = "mc-grid-paging ui-state-default";
                    iconClass = "ui-icon ui-icon-triangle-1-w";
                    title = _paginationPrev;
                    break;
                case PagingType.Next:
                    buttonClass = "mc-grid-paging ui-state-default";
                    iconClass = "ui-icon ui-icon-triangle-1-e";
                    title = _paginationNext;
                    break;
                case PagingType.Last:
                    buttonClass = "mc-grid-paging ui-state-default ui-corner-right";
                    iconClass = "ui-icon ui-icon-seek-end";
                    title = _paginationLast;
                    break;
                default:
                    throw new ArgumentOutOfRangeException("pageNumber");
            }

            buttonClass = string.Format("{0}{1}", buttonClass, (emptyLink) ? " ui-state-disabled" : string.Empty);

            var icon = new TagBuilder("span");
            var button = new TagBuilder("a");

            icon.AddCssClass(iconClass);
            icon.MergeAttribute("title", title);

            button.AddCssClass(buttonClass);
            button.MergeAttribute("href", url);
            button.InnerHtml = icon.ToString(TagRenderMode.Normal);

            return button.ToString(TagRenderMode.Normal);
        }


        private string CreatePagerUrl(int pageNumber)
        {
            return CreateUrl(
                new List<KeyValuePair<string, int>>
                {
                    new KeyValuePair<string, int>(_pageQueryName, pageNumber),
                    new KeyValuePair<string, int>(_pageSizeQueryName, _pagination.PageSize)
                }, null);
        }

        public string CreatePageSizeUrl(int pageSize)
        {
            return CreateUrl(
                new List<KeyValuePair<string, int>>
                {
                    new KeyValuePair<string, int>(_pageSizeQueryName, pageSize)
                },
                new List<string> { _pageQueryName }
            );
        }

        private string CreateUrl(IEnumerable<KeyValuePair<string, int>> pairsToAdd, IEnumerable<string> keysToExclude)
        {
            pairsToAdd = pairsToAdd ?? new List<KeyValuePair<string, int>>();
            keysToExclude = keysToExclude ?? new List<string>();
            var routeValues = new RouteValueDictionary();

            foreach (var key in _viewContext.RequestContext.HttpContext.Request.QueryString.AllKeys.Where(key => key != null))
            {
                routeValues[key] = _viewContext.RequestContext.HttpContext.Request.QueryString[key];
            }

            foreach (var key in keysToExclude.Where(key => key != null).Where(routeValues.ContainsKey))
            {
                routeValues.Remove(key);
            }

            foreach (var pair in pairsToAdd)
            {
                routeValues[pair.Key] = pair.Value;
            }

            var url = UrlHelper.GenerateUrl(null, null, null, routeValues, RouteTable.Routes, _viewContext.RequestContext, true);
            return url;
        }
    }

    public enum PagingType
    {
        First = 0,
        Previous = 1,
        Next = 2,
        Last = 3
    }

    public enum PagerPosition
    {
        Top = 0,
        Bottom = 1,
        Between = 2
    }
}
